#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#include <sys/epoll.h>
#include <vector>
#include "assert.h"
#define PORT 7000
#define QUEUE 20

#define OPEN_MAX 100000


 
int main()
{
    /*
    typedef struct{
        long int fds_bits[32];
    }fd_set;
    */

    int ss = ::socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    //printf("%d\n",INADDR_ANY);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    int socketoptvalue = 1;
        setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (const char*)&socketoptvalue, sizeof(int));

    /**************************
    struct sockaddr {  
     sa_family_t sin_family;//地址族
　　  char sa_data[14]; //14字节，包含套接字中的目标地址和端口信息               
　　 };
    struct sockaddr {  
        sa_family_t sin_family;//地址族
        uint16_t    sin_port;   //16位端口号
        struct in_addr sin_addr;  //32位IP地址
        char          sin_zero[8];  //不使用             
　　 };

    htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)

    inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值，用于sockaddr_in.sin_addr.s_addr。

    inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。
    ****************************/
    if(::bind(ss, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1)
    {
        perror("bind");
        exit(1);
    }
    /*****************************************************
     * #include <sys/socket.h>
        int listen( int sockfd, int backlog);
        sockfd：用于标识一个已捆绑未连接套接口的描述字。
        backlog：等待连接队列的最大长度。
     * ***************************************************/
    if(::listen(ss, QUEUE) == -1)
    {
        perror("listen");
        exit(1);
    }


    struct epoll_event event;
    struct epoll_event *wait_event;
    std::vector<int> sockNumber;
    sockNumber.assign(OPEN_MAX,-1);
    printf("1111\n");
    sockNumber[0] = ss;
    printf("222\n");
    //创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大。
    int epoll_socket = epoll_create(OPEN_MAX-1);
    if( -1 == epoll_socket){
        perror("创建文件描述符失败");
        return 0;
    }
    event.data.fd = ss;
    event.events = EPOLLIN;

    /*epoll的事件注册函数，它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数，
    它不同与select()是在监听事件时告诉内核要监听什么类型的事件，而是在这里先注册要监听的事件类型。
    第一个参数是epoll_create()的返回值，第二个参数表示动作，用三个宏来表示：
    EPOLL_CTL_ADD：注册新的fd到epfd中；
    EPOLL_CTL_MOD：修改已经注册的fd的监听事件；
    EPOLL_CTL_DEL：从epfd中删除一个fd；
    第三个参数是需要监听的fd，
    第四个参数是告诉内核需要监听什么事，
    struct epoll_event结构如下：
    struct epoll_event {
        __uint32_t events;  //Epoll events 
        epoll_data_t data;  // User data variable 
    };*/

    /************************************
    events可以是以下几个宏的集合：
    EPOLLIN ：表示对应的文件描述符可以读（包括对端SOCKET正常关闭）；
    EPOLLOUT：表示对应的文件描述符可以写；
    EPOLLPRI：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）；
    EPOLLERR：表示对应的文件描述符发生错误；
    EPOLLHUP：表示对应的文件描述符被挂断；
    EPOLLET： 将EPOLL设为边缘触发(Edge Triggered)模式，这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里
    ********************************/

    int clRet = epoll_ctl(epoll_socket, EPOLL_CTL_ADD, ss, &event);
    if(-1==clRet){
        perror("注册监听事件类型失败");
    }

    int max = 0;
    while(1){
        wait_event = new epoll_event[max + 1];
        //等待事件的产生，类似于select()调用。
        //参数events用来从内核得到事件的集合，maxevents告之内核这个events有多大，
        //这个maxevents的值不能大于创建epoll_create()时的size，
        //参数timeout是超时时间（毫秒，0会立即返回，-1将不确定，也有说法说是永久阻塞）。
        //该函数返回需要处理的事件数目，如返回0表示已超时。
        clRet = epoll_wait(epoll_socket, wait_event, max+1,-1);

        for(int i = 0; i< clRet; i++){
            if( (ss == wait_event[i].data.fd) && (EPOLLIN == wait_event[i].events&EPOLLIN)){
                struct sockaddr_in client_addr;
                socklen_t length = sizeof(client_addr);

                int clifd = accept(ss, (struct sockaddr *)&client_addr, &length);
                printf("clifd connect %d\n", clifd);
                sockNumber[max+1] = clifd;
                // assert(clifd != -1);
                if(sockNumber[max+1]>0)
                {
                    event.data.fd = sockNumber[max+1];
                    event.events = EPOLLIN;
                    int ret1 = epoll_ctl(epoll_socket, EPOLL_CTL_ADD, sockNumber[max+1], &event);
                    max++;
                    if(-1==ret1)
                    {
                        perror("新连接的客户端注册失败");
                    }
                    printf("客户端%d上线\n",max);
                }
            }else if(wait_event[i].data.fd > 3 && (EPOLLIN == wait_event[i].events & (EPOLLIN|EPOLLERR))){
                char buffer[1024];   
                memset(buffer, 0 ,sizeof(buffer));
                int len = ::recv(wait_event[i].data.fd, buffer, sizeof(buffer), 0);
                if(len <= 0){
                    // printf("cli back %d\n", fds[i]);
                    for(int j=1;j<=max;j++){
                        if(wait_event[i].data.fd==sockNumber[j])
                        {
                            clRet=epoll_ctl(epoll_socket,EPOLL_CTL_DEL,wait_event[i].data.fd,wait_event+i);
                            if(len == 0)
                                printf("客户端%d下线\n",j);
                            else
                                printf("cli recv fail %d\n", j);
                            // sockNumber[j] =sockNumber[max];
                            close(sockNumber[j]);
                            sockNumber[j] =-1;
                            sockNumber.erase(sockNumber.begin() + j);
                            max--;
                            // usleep(50000);
                        }
                    }

                    continue;

                };
                printf("recv from cli :%s", buffer);
                int ret = send(wait_event[i].data.fd, "ok", 2, 0);
                if(ret < 0){
                    printf("send fail ret = %d\n",ret);
                }
            }
        }

        delete[] wait_event;

    }
    ::close(epoll_socket);
    ::close(ss);
    return 0;
}